Skip to content

feat: HN news reader with offline-first feed and premium UI#11

Merged
maximcoding merged 17 commits intomasterfrom
feature/hn-news-reader
Mar 25, 2026
Merged

feat: HN news reader with offline-first feed and premium UI#11
maximcoding merged 17 commits intomasterfrom
feature/hn-news-reader

Conversation

@maximcoding
Copy link
Copy Markdown
Owner

  • Home screen: replaces demo feed with live Hacker News Algolia API (Zod schema + mapper → React Query nearRealtime + MMKV persister)
  • StoryScreen: full in-app WebView reader with back/close navigation, falls back to HN discussion page for link-less posts
  • Settings: premium redesign — glassmorphism-style profile card, icon badges per row (sun/moon/globe/info/logout), improved dividers
  • Auth: social login buttons with text labels, Apple icon viewport fix, subtitle wrapping fix, demo hint single-line, terms padding fix
  • Icons: add check, sun, moon, globe, info, logout, layers SVGs
  • i18n: updated home section labels across en/de/ru
  • Navigation: HOME_STORY route added to root stack param list
  • Install react-native-webview for in-app article reading
  • Fix Jest config: add react-native-webview to transformIgnorePatterns and add WebView mock to jest.setup.js

Description

Brief description of the change.

Checklist

  • npm run lint passes (Biome — biome check .)
  • npm test passes
  • npx tsc --noEmit passes
  • No unrelated changes (refactors/formatting in a separate PR)
  • If user-facing: added/updated entry in CHANGELOG.md under ## Unreleased

maximcoding and others added 17 commits March 25, 2026 01:07
- Home screen: replaces demo feed with live Hacker News Algolia API
  (Zod schema + mapper → React Query nearRealtime + MMKV persister)
- StoryScreen: full in-app WebView reader with back/close navigation,
  falls back to HN discussion page for link-less posts
- Settings: premium redesign — glassmorphism-style profile card,
  icon badges per row (sun/moon/globe/info/logout), improved dividers
- Auth: social login buttons with text labels, Apple icon viewport fix,
  subtitle wrapping fix, demo hint single-line, terms padding fix
- Icons: add check, sun, moon, globe, info, logout, layers SVGs
- i18n: updated home section labels across en/de/ru
- Navigation: HOME_STORY route added to root stack param list
- Install react-native-webview for in-app article reading
- Fix Jest config: add react-native-webview to transformIgnorePatterns
  and add WebView mock to jest.setup.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ad code

- Replace magic numbers in StoryScreen with design token constants
  (HEADER_HEIGHT=spacing.xxxxxl, ICON_SIZE=spacing.lg, PROGRESS_BAR_HEIGHT=spacing.xxs)
- Extract formatRelativeTime to shared/utils to eliminate duplication
  between hn.mappers and useFeedQuery
- Rename ActivityType → AccentVariant; align values with theme color keys
  ('primary'/'info'/'warning' replacing 'task'/'message'/'alert')
- Add useMemo/useCallback throughout StoryScreen and useFeedQuery
- Add attachLogging(hnClient) for dev HTTP visibility
- Remove dead code: TAB_COMPONENTS route, UIKitScreen, redundant subtitle??undefined
- Add 14 unit tests for parseDomain and mapHnHitToFeedItem
- All 41 tests pass, zero TypeScript errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace JSX-based dynamic config with static config objects:
- Inline all thin stack wrappers (auth, onboarding, settings, home-tabs)
  into a single root-navigator.tsx using createNativeStackNavigator({screens})
- Replace NavigationContainer + AppLayout with createStaticNavigation(RootStack)
- Add useT() to AnimatedTabBar for label derivation — no more tabBarLabel
  prop needed in tab screen options
- Remove dead routes: HOME_STACK, HOME_TABS, SETTINGS_ROOT/LANGUAGE/THEME
- Delete 6 files: auth-stack, onboarding-stack, settings-stack, home-stack,
  home-tabs, AppLayout
- Delete 3 dead feature param-list directories (types now inferred by RN)

Zero type errors, 41 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-fix 17 files: import organization, trailing commas, line wrapping.
Zero lint errors, zero type errors, 41 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract useShimmer() → src/shared/hooks/useShimmer.ts
  Pure Animated.Value pulse hook, no feature coupling
- Extract SectionHeader → src/shared/components/ui/SectionHeader.tsx
  Label row + optional offline/synced status pill; reusable by any list screen
- HomeScreen imports both from shared; removes ~70 lines of inline code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
showPassword, emailFocused, passFocused all replaced by useToggle()
from shared/hooks. Toggle handler is now a stable ref (toggleShowPassword)
instead of an inline arrow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate zLoginRequest validation in useLoginMutation
  (AuthService.login already validates; hook was parsing twice)
- Remove hardcoded mutationKey ['auth','login'] — optional and not in authKeys
- Flatten nested offline guard: if (!mock) { if (offline) } → if (!mock && offline)
- Move SessionSchema out of useAuthSessionQuery into auth.schemas.ts
  where all Zod schemas belong (zSessionResponse / SessionResponse)
- Remove .catch(() => undefined) on qc.cancelQueries() in useLogout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move useAppLaunch → src/session/ (imports from session layer)
- Move useShimmer → src/features/home/hooks/ (only used in HomeScreen)
- Delete useBackHandler from shared (zero consumers; superseded by
  navigation/helpers/use-back-handler)
- Remove SESSION_RELATED_QUERY_TAGS from shared/constants (cross-feature
  coupling); replace with feature-local tag arrays in useLoginMutation
  and useUpdateProfile, each scoped to their own tag map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace TouchableOpacity with Pressable throughout (ScreenHeader,
  AuthScreen) per react-native-skills guidelines; use style callback
  for pressed opacity feedback instead of activeOpacity
- Remove activeOpacity={1} on social buttons — Reanimated handles
  press animation; Pressable has no built-in feedback
- Extract static inline styles in HomeScreen to StyleSheet.create():
  StoryCard meta row (rendered in FlashList on every item) and
  ListFooter height (constant value)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Memoization:
- ThemePickerModal/LanguagePickerModal: extract ThemeOptionRow/
  LanguageOptionRow as memo'd components — onPress is now a stable
  useCallback([opt.mode/code, onSelect]) instead of a new closure
  created per render per map item
- ThemeScreen: extract ModeButton memo component for same reason
- LanguageScreen: replace 3 inline arrow functions with named
  useCallback handlers (handleEnglish/Russian/German)
- StoryScreen: extract handleClose useCallback for the dismiss button

Accessibility:
- ThemePickerModal/LanguagePickerModal items: add accessibilityRole,
  accessibilityLabel (translated option name), accessibilityState
- SettingsRow: add accessibilityRole (button when pressable, none
  otherwise) and accessibilityLabel derived from label prop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HalfSheet is a generic animated bottom-sheet UI primitive with no
navigation knowledge — it imports only from shared/theme. Living in
navigation/modals/ forced feature screens to import from navigation/
just to render a container component.

- Move navigation/modals/half-sheet.tsx → shared/components/ui/HalfSheet.tsx
- Update ThemePickerModal and LanguagePickerModal imports
- Remove dead `export * from './modals/half-sheet'` in navigation/index.ts
  (default exports are not captured by export *, so it was a no-op)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same issue as HalfSheet: GlobalModal imports only from shared/theme
and shared/components/ui — no navigation dependency. Feature screens
that use it would have to import from navigation/ for a pure UI shell.

- Move navigation/modals/global-modal.tsx → shared/components/ui/GlobalModal.tsx
- Remove export from navigation/index.ts (no consumers, dead export)

AnimatedTabBar stays in navigation/ — it depends on BottomTabBarProps,
navigation.emit(), and ROUTES, making it genuine navigation infrastructure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ptions

Naming:
- BootstrapRoute → InitialRoute
- getBootstrapRoute() → getInitialRoute()
- useBootstrapRoute() → useInitialRoute() (file renamed accordingly)
- All call sites updated: root-navigator, navigation-persistence, useAppLaunch

Result: initialRouteName: getInitialRoute() reads as intended at the
call site; no more indirection through a "bootstrap" name for what is
simply the navigator's starting screen.

Dead code:
- Delete src/navigation/options/ entirely — useNav(), navigation.presets.ts,
  navigation.tokens.ts, tabOptions.tsx were all helpers for the old
  dynamic navigator config, superseded by the static config migration.
  Zero external consumers remained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace stale dynamic-config references (NavigationContainer, per-feature
  param-list files, useBootstrapRoute) with current static config API
- Add v7-specific rules: screens object, groups vs nesting, if property
  for conditional auth rendering, .with() for dynamic options
- Add params rules: JSON-serializable, IDs only, reserved key list,
  setParams vs replaceParams, popTo for back-passing data
- Add navigation action guide: navigate vs push vs popTo vs popToTop
- Add screen lifecycle rules: useFocusEffect pattern, useIsFocused,
  addListener cleanup
- Add TypeScript rules: type not interface, no useNavigation<T>(),
  CompositeScreenProps, NavigatorScreenParams
- Update half-sheet entry to reference HalfSheet shared component
- Add InteractionManager rule for post-navigation heavy work
- Add empty-directory rule for navigation folder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Navigation:
- Revert root-navigator to dynamic JSX config (Stack.Navigator/Screen)
  with typed generic params; useInitialRoute() hook in component body
- Add HomeTabParamList to root-param-list.ts; export from index.ts
- Update NavigationRoot.tsx and AnimatedTabBar.tsx accordingly

Theme:
- Remove unused italic font variant from fonts.ts
- Add brand.ts token file; export from theme index
- Sync dark/light theme with new token

Auth / User:
- Export AUTH_SESSION_TAGS from auth/api/keys.ts (satisfies AuthTag[])
- Export USER_UPDATE_TAGS from user/api/keys.ts
- Remove dead oauth-brand-colors.ts constant file
- Update useLoginMutation and useUpdateProfile to import named tag arrays

Docs / rules:
- Update navigation.md to reflect dynamic config (not static)
- Minor fixes in CLAUDE.md, react-query.md, shared-services.md, development.md
- Remove rn-architect agent (superseded by rn-code-reviewer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@maximcoding maximcoding merged commit 189f752 into master Mar 25, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant